/*-------------------------------------------------------------------------
 *
 * statscmds.c
 *	  创建和更改扩展统计对象的命令
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/statscmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/relation.h"
#include "access/relscan.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "statistics/statistics.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"


static char *fc_ChooseExtendedStatisticName(const char *fc_name1, const char *fc_name2,
										 const char *fc_label, Oid fc_namespaceid);
static char *fc_ChooseExtendedStatisticNameAddition(List *fc_exprs);


/* qsort 比较器用于 CreateStatistics 中的 attnums */
static int fc_compare_int16(const void *fc_a, const void *fc_b)
{
	int			fc_av = *(const int16 *) fc_a;
	int			fc_bv = *(const int16 *) fc_b;

	/* 如果 int 的宽度大于 int16，这个不会溢出 */
	return (fc_av - fc_bv);
}

/*
 *		CREATE STATISTICS
 */
ObjectAddress CreateStatistics(CreateStatsStmt *fc_stmt)
{
	int16		fc_attnums[STATS_MAX_DIMENSIONS];
	int			fc_nattnums = 0;
	int			fc_numcols;
	char	   *fc_namestr;
	NameData	fc_stxname;
	Oid			fc_statoid;
	Oid			fc_namespaceId;
	Oid			fc_stxowner = GetUserId();
	HeapTuple	fc_htup;
	Datum		fc_values[Natts_pg_statistic_ext];
	bool		fc_nulls[Natts_pg_statistic_ext];
	int2vector *fc_stxkeys;
	List	   *fc_stxexprs = NIL;
	Datum		fc_exprsDatum;
	Relation	fc_statrel;
	Relation	fc_rel = NULL;
	Oid			fc_relid;
	ObjectAddress fc_parentobject,
				fc_myself;
	Datum		fc_types[4];		/* 每种可能的统计类型各一个 */
	int			fc_ntypes;
	ArrayType  *fc_stxkind;
	bool		fc_build_ndistinct;
	bool		fc_build_dependencies;
	bool		fc_build_mcv;
	bool		fc_build_expressions;
	bool		fc_requested_type = false;
	int			fc_i;
	ListCell   *fc_cell;
	ListCell   *fc_cell2;

	Assert(IsA(fc_stmt, CreateStatsStmt));

	/*
	 * 检查 FROM 子句。目前，我们只允许它是单个简单表，但以后可能会允许多个表和 JOIN 语法。
	 * 语法已经为此做好了准备，因此我们必须在这里检查
	 * 我们得到的是否是我们可以支持的。
	 */
	if (list_length(fc_stmt->relations) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("only a single relation is allowed in CREATE STATISTICS")));

	foreach(fc_cell, fc_stmt->relations)
	{
		Node	   *fc_rln = (Node *) lfirst(fc_cell);

		if (!IsA(fc_rln, RangeVar))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("only a single relation is allowed in CREATE STATISTICS")));

		/*
		 * CREATE STATISTICS 将影响未来的执行计划，但不会干扰当前正在执行的计划。
		 * 所以，只需对关系采取 ShareUpdateExclusiveLock，
		 * 与 ANALYZE 和其他设置统计信息的 DDL 冲突，但不与普通查询冲突。
		 */
		fc_rel = relation_openrv((RangeVar *) fc_rln, ShareUpdateExclusiveLock);

		/* 限制到允许的关系类型 */
		if (fc_rel->rd_rel->relkind != RELKIND_RELATION &&
			fc_rel->rd_rel->relkind != RELKIND_MATVIEW &&
			fc_rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
			fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot define statistics for relation \"%s\"",
							RelationGetRelationName(fc_rel)),
					 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));

		/* 您必须拥有关系才能在其上创建统计信息 */
		if (!pg_class_ownercheck(RelationGetRelid(fc_rel), fc_stxowner))
			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(fc_rel->rd_rel->relkind),
						   RelationGetRelationName(fc_rel));

		/* 不允许在系统目录上创建统计信息 */
		if (!allowSystemTableMods && IsSystemRelation(fc_rel))
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("permission denied: \"%s\" is a system catalog",
							RelationGetRelationName(fc_rel))));
	}

	Assert(fc_rel);
	fc_relid = RelationGetRelid(fc_rel);

	/*
	 * 如果节点有名称，将其拆分并确定创建命名空间。
	 * 如果没有（这是语法未考虑的可能性，但可以通过 "CREATE TABLE ... (LIKE)" 命令发生），
	 * 那么我们将对象放在与关系相同的命名空间中，并为其生成一个名称。
	 */
	if (fc_stmt->defnames)
		fc_namespaceId = QualifiedNameGetCreationNamespace(fc_stmt->defnames,
														&fc_namestr);
	else
	{
		fc_namespaceId = RelationGetNamespace(fc_rel);
		fc_namestr = fc_ChooseExtendedStatisticName(RelationGetRelationName(fc_rel),
											  fc_ChooseExtendedStatisticNameAddition(fc_stmt->exprs),
											  "stat",
											  fc_namespaceId);
	}
	namestrcpy(&fc_stxname, fc_namestr);

	/*
	 * 处理统计对象已经存在的可能性。
	 */
	if (SearchSysCacheExists2(STATEXTNAMENSP,
							  CStringGetDatum(fc_namestr),
							  ObjectIdGetDatum(fc_namespaceId)))
	{
		if (fc_stmt->if_not_exists)
		{
			/*
			 * 由于统计对象不是扩展的成员（见下文评论），因此这里不需要 checkMembershipInCurrentExtension。
			 */
			ereport(NOTICE,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("statistics object \"%s\" already exists, skipping",
							fc_namestr)));
			relation_close(fc_rel, NoLock);
			return InvalidObjectAddress;
		}

		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("statistics object \"%s\" already exists", fc_namestr)));
	}

	/*
	 * 确保不会使用超过 STATS_MAX_DIMENSIONS 列。可能会有重复等，
	 * 但我们稍后会处理这些问题。
	 */
	fc_numcols = list_length(fc_stmt->exprs);
	if (fc_numcols > STATS_MAX_DIMENSIONS)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("cannot have more than %d columns in statistics",
						STATS_MAX_DIMENSIONS)));

	/*
	 * 将表达式列表转换为简单的 attnums 数组，并保留
	 * 更复杂表达式的列表。在此过程中，强制一些
	 * 约束 - 我们不允许在系统属性上进行扩展统计，
	 * 并且要求数据类型具有小于运算符。
	 *
	 * 有许多方法可以将简单属性引用“掩盖”为
	 * 表达式，例如“(a+0)”等。我们不可能检测到所有
	 * 的情况，但我们至少处理带括号的简单情况。
	 * 如果用户决心如此（例如“(a+0)”示例），总会有办法绕过，
	 * 但这使得它在某种程度上与
	 * 索引处理属性/表达式的方式保持一致。
	 */
	foreach(fc_cell, fc_stmt->exprs)
	{
		StatsElem  *fc_selem = lfirst_node(StatsElem, fc_cell);

		if (fc_selem->name)		/* 列引用 */
		{
			char	   *fc_attname;
			HeapTuple	fc_atttuple;
			Form_pg_attribute fc_attForm;
			TypeCacheEntry *fc_type;

			fc_attname = fc_selem->name;

			fc_atttuple = SearchSysCacheAttName(fc_relid, fc_attname);
			if (!HeapTupleIsValid(fc_atttuple))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" does not exist",
								fc_attname)));
			fc_attForm = (Form_pg_attribute) GETSTRUCT(fc_atttuple);

			/* 不允许在扩展统计中使用系统属性 */
			if (fc_attForm->attnum <= 0)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("statistics creation on system columns is not supported")));

			/* 不允许没有小于运算符的数据类型 */
			fc_type = lookup_type_cache(fc_attForm->atttypid, TYPECACHE_LT_OPR);
			if (fc_type->lt_opr == InvalidOid)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
								fc_attname, format_type_be(fc_attForm->atttypid))));

			fc_attnums[fc_nattnums] = fc_attForm->attnum;
			fc_nattnums++;
			ReleaseSysCache(fc_atttuple);
		}
		else if (IsA(fc_selem->expr, Var)) /* 括号中的列引用 */
		{
			Var		   *fc_var = (Var *) fc_selem->expr;
			TypeCacheEntry *fc_type;

			/* 不允许在扩展统计中使用系统属性 */
			if (fc_var->varattno <= 0)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("statistics creation on system columns is not supported")));

			/* 不允许没有小于运算符的数据类型 */
			fc_type = lookup_type_cache(fc_var->vartype, TYPECACHE_LT_OPR);
			if (fc_type->lt_opr == InvalidOid)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
								get_attname(fc_relid, fc_var->varattno, false), format_type_be(fc_var->vartype))));

			fc_attnums[fc_nattnums] = fc_var->varattno;
			fc_nattnums++;
		}
		else					/* 表达式 */
		{
			Node	   *fc_expr = fc_selem->expr;
			Oid			fc_atttype;
			TypeCacheEntry *fc_type;
			Bitmapset  *fc_attnums = NULL;
			int			fc_k;

			Assert(fc_expr != NULL);

			/* 不允许引用系统属性的表达式。 */
			pull_varattnos(fc_expr, 1, &fc_attnums);

			fc_k = -1;
			while ((fc_k = bms_next_member(fc_attnums, fc_k)) >= 0)
			{
				AttrNumber	fc_attnum = fc_k + FirstLowInvalidHeapAttributeNumber;

				if (fc_attnum <= 0)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("statistics creation on system columns is not supported")));
			}

			/*
			 * 不允许没有小于操作符的数据类型。
			 *
			 * 我们对单个表达式的统计数据忽略这一点，在这种情况下我们只会构建常规统计数据（且该代码可以处理这样的数据类型）。
			 */
			if (list_length(fc_stmt->exprs) > 1)
			{
				fc_atttype = exprType(fc_expr);
				fc_type = lookup_type_cache(fc_atttype, TYPECACHE_LT_OPR);
				if (fc_type->lt_opr == InvalidOid)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("expression cannot be used in multivariate statistics because its type %s has no default btree operator class",
									format_type_be(fc_atttype))));
			}

			fc_stxexprs = lappend(fc_stxexprs, fc_expr);
		}
	}

	/*
	 * 解析统计类型。
	 *
	 * 首先检查如果这是单个表达式，那么没有指定统计类型（我们不允许简单的 CREATE STATISTICS 形式）。
	 */
	if ((list_length(fc_stmt->exprs) == 1) && (list_length(fc_stxexprs) == 1))
	{
		/* 未指定统计类型 */
		if (list_length(fc_stmt->stat_types) > 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("when building statistics on a single expression, statistics kinds may not be specified")));
	}

	/* 好的，让我们检查一下我们是否识别统计类型。 */
	fc_build_ndistinct = false;
	fc_build_dependencies = false;
	fc_build_mcv = false;
	foreach(fc_cell, fc_stmt->stat_types)
	{
		char	   *fc_type = strVal(lfirst(fc_cell));

		if (strcmp(fc_type, "ndistinct") == 0)
		{
			fc_build_ndistinct = true;
			fc_requested_type = true;
		}
		else if (strcmp(fc_type, "dependencies") == 0)
		{
			fc_build_dependencies = true;
			fc_requested_type = true;
		}
		else if (strcmp(fc_type, "mcv") == 0)
		{
			fc_build_mcv = true;
			fc_requested_type = true;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("unrecognized statistics kind \"%s\"",
							fc_type)));
	}

	/*
	 * 如果没有指定统计类型，则构建所有类型（但仅当统计数据定义在多个列/表达式上时）。
	 */
	if ((!fc_requested_type) && (fc_numcols >= 2))
	{
		fc_build_ndistinct = true;
		fc_build_dependencies = true;
		fc_build_mcv = true;
	}

	/*
	 * 当存在非平凡表达式时，自动生成表达式统计数据。这可以计算考虑每个子句估算的统计数据的良好估计（例如，函数依赖关系）。
	 */
	fc_build_expressions = (list_length(fc_stxexprs) > 0);

	/*
	 * 检查声明中是否指定了至少两列，或者我们是否在单个表达式上构建统计数据。
	 */
	if ((fc_numcols < 2) && (list_length(fc_stxexprs) != 1))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("extended statistics require at least 2 columns")));

	/*
	 * 对 attnums 进行排序，这使得检测重复项变得稍微容易一些，并且没有任何坏处（这与内容无关，不像索引等）。
	 */
	qsort(fc_attnums, fc_nattnums, sizeof(int16), fc_compare_int16);

	/*
	 * 检查列列表中的重复项。 attnums 已排序，因此只需检查连续元素。
	 */
	for (fc_i = 1; fc_i < fc_nattnums; fc_i++)
	{
		if (fc_attnums[fc_i] == fc_attnums[fc_i - 1])
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_COLUMN),
					 errmsg("duplicate column name in statistics definition")));
	}

	/*
	 * 检查重复表达式。我们进行两次循环，计算每个表达式的出现次数。这是 O(N^2)，但我们仅允许少量表达式，并且它不会被频繁执行。
	 *
	 * XXX 我们没有交叉检查属性和表达式，因为这似乎不值得。从原则上讲，我们可以检查表达式是否不包含简单的属性引用，如“(a)”，但推理与为什么我们不烦恼于从表达式中提取列相似。它要么代价高昂，要么对于决心用户来说很容易被绕过，如果我们允许这样的统计数据，则没有风险（统计数据没用，但无害）。
	 */
	foreach(fc_cell, fc_stxexprs)
	{
		Node	   *fc_expr1 = (Node *) lfirst(fc_cell);
		int			fc_cnt = 0;

		foreach(fc_cell2, fc_stxexprs)
		{
			Node	   *fc_expr2 = (Node *) lfirst(fc_cell2);

			if (equal(fc_expr1, fc_expr2))
				fc_cnt += 1;
		}

		/* 每个表达式至少应找到自身 */
		Assert(fc_cnt >= 1);

		if (fc_cnt > 1)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_COLUMN),
					 errmsg("duplicate expression in statistics definition")));
	}

	/* 形成已排序列列表的 int2vector 表示 */
	fc_stxkeys = buildint2vector(fc_attnums, fc_nattnums);

	/* 构造启用的统计类型的字符数组 */
	fc_ntypes = 0;
	if (fc_build_ndistinct)
		fc_types[fc_ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
	if (fc_build_dependencies)
		fc_types[fc_ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
	if (fc_build_mcv)
		fc_types[fc_ntypes++] = CharGetDatum(STATS_EXT_MCV);
	if (fc_build_expressions)
		fc_types[fc_ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
	Assert(fc_ntypes > 0 && fc_ntypes <= lengthof(fc_types));
	fc_stxkind = construct_array(fc_types, fc_ntypes, CHAROID, 1, true, TYPALIGN_CHAR);

	/* 将表达式（如有）转换为文本数据项 */
	if (fc_stxexprs != NIL)
	{
		char	   *fc_exprsString;

		fc_exprsString = nodeToString(fc_stxexprs);
		fc_exprsDatum = CStringGetTextDatum(fc_exprsString);
		pfree(fc_exprsString);
	}
	else
		fc_exprsDatum = (Datum) 0;

	fc_statrel = table_open(StatisticExtRelationId, RowExclusiveLock);

	/*
	 * 一切看起来都不错，因此让我们构建 pg_statistic_ext 元组。
	 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_statoid = GetNewOidWithIndex(fc_statrel, StatisticExtOidIndexId,
								 Anum_pg_statistic_ext_oid);
	fc_values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(fc_statoid);
	fc_values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(fc_relid);
	fc_values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&fc_stxname);
	fc_values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(fc_namespaceId);
	fc_values[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(-1);
	fc_values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(fc_stxowner);
	fc_values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(fc_stxkeys);
	fc_values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(fc_stxkind);

	fc_values[Anum_pg_statistic_ext_stxexprs - 1] = fc_exprsDatum;
	if (fc_exprsDatum == (Datum) 0)
		fc_nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;

	/* 将其插入 pg_statistic_ext */
	fc_htup = heap_form_tuple(fc_statrel->rd_att, fc_values, fc_nulls);
	CatalogTupleInsert(fc_statrel, fc_htup);
	heap_freetuple(fc_htup);

	relation_close(fc_statrel, RowExclusiveLock);

	/*
	 * 我们过去也会创建 pg_statistic_ext_data 元组，但不清楚 stxdinherit 标志应该具有什么值（这取决于 rel 是否分区、是否包含数据等）。
	 */

	InvokeObjectPostCreateHook(StatisticExtRelationId, fc_statoid, 0);

	/*
	 * 使 relcache 无效，以便其他人看到新的统计对象。
	 */
	CacheInvalidateRelcache(fc_rel);

	relation_close(fc_rel, NoLock);

	/*
	 * 对统计中使用的每列添加 AUTO 依赖关系，以便如果其中任何列被删除，则统计对象会消失。
	 */
	ObjectAddressSet(fc_myself, StatisticExtRelationId, fc_statoid);

	/* 为普通列引用添加依赖关系 */
	for (fc_i = 0; fc_i < fc_nattnums; fc_i++)
	{
		ObjectAddressSubSet(fc_parentobject, RelationRelationId, fc_relid, fc_attnums[fc_i]);
		recordDependencyOn(&fc_myself, &fc_parentobject, DEPENDENCY_AUTO);
	}

	/*
	 * 如果列上没有依赖关系，则为统计对象提供对整个表的自动依赖关系。在大多数情况下，这将是多余的，但如果统计表达式不包含任何Vars（这可能看起来奇怪但确实可能），那么这可能不是多余的。这与我们在index_create中对索引所做的处理是一致的。
	 *
	 * XXX 我们故意没有考虑表达式，在增加这种依赖关系之前，因为recordDependencyOnSingleRelExpr可能不会为整个行Vars创建任何依赖关系。
	 */
	if (!fc_nattnums)
	{
		ObjectAddressSet(fc_parentobject, RelationRelationId, fc_relid);
		recordDependencyOn(&fc_myself, &fc_parentobject, DEPENDENCY_AUTO);
	}

	/*
	 * 存储与统计表达式中提到的任何内容的依赖关系，就像我们对指数表达式所做的那样。
	 */
	if (fc_stxexprs)
		recordDependencyOnSingleRelExpr(&fc_myself,
										(Node *) fc_stxexprs,
										fc_relid,
										DEPENDENCY_NORMAL,
										DEPENDENCY_AUTO, false);

	/*
	 * 还要添加对命名空间和所有者的依赖关系。这是必要的，因为统计对象可能与基础表的命名空间和/或所有者不同。
	 */
	ObjectAddressSet(fc_parentobject, NamespaceRelationId, fc_namespaceId);
	recordDependencyOn(&fc_myself, &fc_parentobject, DEPENDENCY_NORMAL);

	recordDependencyOnOwner(StatisticExtRelationId, fc_statoid, fc_stxowner);

	/*
	 * XXX 这里可能也应该有一个recordDependencyOnCurrentExtension调用，但我们必须添加对ALTER EXTENSION ADD/DROP STATISTICS的支持，这比看起来更费事。
	 */

	/* 添加任何请求的注释 */
	if (fc_stmt->stxcomment != NULL)
		CreateComments(fc_statoid, StatisticExtRelationId, 0,
					   fc_stmt->stxcomment);

	/* 返回统计对象的地址 */
	return fc_myself;
}

/*
 *		ALTER STATISTICS
 */
ObjectAddress AlterStatistics(AlterStatsStmt *fc_stmt)
{
	Relation	fc_rel;
	Oid			fc_stxoid;
	HeapTuple	fc_oldtup;
	HeapTuple	fc_newtup;
	Datum		fc_repl_val[Natts_pg_statistic_ext];
	bool		fc_repl_null[Natts_pg_statistic_ext];
	bool		fc_repl_repl[Natts_pg_statistic_ext];
	ObjectAddress fc_address;
	int			fc_newtarget = fc_stmt->stxstattarget;

	/* 将统计目标限制在一个合理的范围内 */
	if (fc_newtarget < -1)
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("statistics target %d is too low",
						fc_newtarget)));
	}
	else if (fc_newtarget > 10000)
	{
		fc_newtarget = 10000;
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("lowering statistics target to %d",
						fc_newtarget)));
	}

	/* 查找统计对象的OID */
	fc_stxoid = get_statistics_object_oid(fc_stmt->defnames, fc_stmt->missing_ok);

	/*
	 * 如果我们来到这里而OID无效，则意味着统计对象不存在，但命令指定了IF EXISTS。所以报告这只是一个简单的NOTICE，我们就完成了。
	 */
	if (!OidIsValid(fc_stxoid))
	{
		char	   *fc_schemaname;
		char	   *fc_statname;

		Assert(fc_stmt->missing_ok);

		DeconstructQualifiedName(fc_stmt->defnames, &fc_schemaname, &fc_statname);

		if (fc_schemaname)
			ereport(NOTICE,
					(errmsg("statistics object \"%s.%s\" does not exist, skipping",
							fc_schemaname, fc_statname)));
		else
			ereport(NOTICE,
					(errmsg("statistics object \"%s\" does not exist, skipping",
							fc_statname)));

		return InvalidObjectAddress;
	}

	/* 搜索pg_statistic_ext */
	fc_rel = table_open(StatisticExtRelationId, RowExclusiveLock);

	fc_oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_stxoid));
	if (!HeapTupleIsValid(fc_oldtup))
		elog(ERROR, "cache lookup failed for extended statistics object %u", fc_stxoid);

	/* 必须是现有统计对象的所有者 */
	if (!pg_statistics_object_ownercheck(fc_stxoid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
					   NameListToString(fc_stmt->defnames));

	/* 构建新元组。 */
	memset(fc_repl_val, 0, sizeof(fc_repl_val));
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	/* 替换stxstattarget列 */
	fc_repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
	fc_repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(fc_newtarget);

	fc_newtup = heap_modify_tuple(fc_oldtup, RelationGetDescr(fc_rel),
							   fc_repl_val, fc_repl_null, fc_repl_repl);

	/* 更新系统目录。 */
	CatalogTupleUpdate(fc_rel, &fc_newtup->t_self, fc_newtup);

	InvokeObjectPostAlterHook(StatisticExtRelationId, fc_stxoid, 0);

	ObjectAddressSet(fc_address, StatisticExtRelationId, fc_stxoid);

	/*
	 * 注意：因为我们只支持修改统计目标，而不支持其他字段，所以没有必要更新依赖关系。
	 */

	heap_freetuple(fc_newtup);
	ReleaseSysCache(fc_oldtup);

	table_close(fc_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * 删除pg_statistic_ext_data目录中的条目。我们不知道该行是否存在，所以不需要报错。
 */
void RemoveStatisticsDataById(Oid fc_statsOid, bool fc_inh)
{
	Relation	fc_relation;
	HeapTuple	fc_tup;

	fc_relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(fc_statsOid),
						  BoolGetDatum(fc_inh));

	/* 我们不知道inh值的数据行是否存在。 */
	if (HeapTupleIsValid(fc_tup))
	{
		CatalogTupleDelete(fc_relation, &fc_tup->t_self);

		ReleaseSysCache(fc_tup);
	}

	table_close(fc_relation, RowExclusiveLock);
}

/*
 * 统计对象删除的核心部分。
 */
void RemoveStatisticsById(Oid fc_statsOid)
{
	Relation	fc_relation;
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Form_pg_statistic_ext fc_statext;
	Oid			fc_relid;

	/*
	 * 删除pg_statistic_ext元组。同时在相关表上发送一个缓存失效信号，以便重建依赖计划。
	 */
	fc_relation = table_open(StatisticExtRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_statsOid));

	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for statistics object %u", fc_statsOid);

	fc_statext = (Form_pg_statistic_ext) GETSTRUCT(fc_tup);
	fc_relid = fc_statext->stxrelid;

	/*
	 * 删除持有实际统计数据的pg_statistic_ext_data元组。可能存在有/没有继承的数据，因此尝试同时删除两者。我们首先锁定用户表，以防止其他进程（例如DROP STATISTICS）同时删除该行。
	 */
	fc_rel = table_open(fc_relid, ShareUpdateExclusiveLock);

	RemoveStatisticsDataById(fc_statsOid, true);
	RemoveStatisticsDataById(fc_statsOid, false);

	CacheInvalidateRelcacheByRelid(fc_relid);

	CatalogTupleDelete(fc_relation, &fc_tup->t_self);

	ReleaseSysCache(fc_tup);

	/* 锁定直到事务结束。 */
	table_close(fc_rel, NoLock);

	table_close(fc_relation, RowExclusiveLock);
}

/*
 * 为新的统计对象选择一个不冲突的名称。
 *
 * name1、name2 和 label 的使用方法与 makeObjectName() 相同，
 * 只是 label 不能为 NULL；如果需要以在指定命名空间中创建唯一名称，
 * 将在 label 后追加数字。
 *
 * 返回一个 palloc 分配的字符串。
 *
 * 注意：从理论上讲，仍然有可能发生冲突，如果其他人同时选择了相同的名称。
 * 实际上，这种情况发生的可能性相当小，尤其是当持有对 name1 所标识的关系
 * 的共享更新独占锁时。然而，如果在单个命令中选择多个名称，
 * 最好在选择下一个名称之前创建新对象并执行 CommandCounterIncrement！
 */
static char * fc_ChooseExtendedStatisticName(const char *fc_name1, const char *fc_name2,
							const char *fc_label, Oid fc_namespaceid)
{
	int			fc_pass = 0;
	char	   *fc_stxname = NULL;
	char		fc_modlabel[NAMEDATALEN];

	/* 首先尝试未修改的标签 */
	strlcpy(fc_modlabel, fc_label, sizeof(fc_modlabel));

	for (;;)
	{
		Oid			fc_existingstats;

		fc_stxname = makeObjectName(fc_name1, fc_name2, fc_modlabel);

		fc_existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
										PointerGetDatum(fc_stxname),
										ObjectIdGetDatum(fc_namespaceid));
		if (!OidIsValid(fc_existingstats))
			break;

		/* 发现冲突，因此尝试一个新的名称组件 */
		pfree(fc_stxname);
		snprintf(fc_modlabel, sizeof(fc_modlabel), "%s%d", fc_label, ++fc_pass);
	}

	return fc_stxname;
}

/*
 * 针对新的统计对象生成 "name2"，给定其列名称列表。
 * 这将与父表名称和合适的标签一起传递给 ChooseExtendedStatisticName。
 *
 * 我们知道实际使用的字符数少于 NAMEDATALEN，因此在生成
 * 该数量后，我们可以截断结果。
 *
 * XXX 另见 ChooseForeignKeyConstraintNameAddition 和
 * ChooseIndexNameAddition。
 */
static char * fc_ChooseExtendedStatisticNameAddition(List *fc_exprs)
{
	char		fc_buf[NAMEDATALEN * 2];
	int			fc_buflen = 0;
	ListCell   *fc_lc;

	fc_buf[0] = '\0';
	foreach(fc_lc, fc_exprs)
	{
		StatsElem  *fc_selem = (StatsElem *) lfirst(fc_lc);
		const char *fc_name;

		/* 它应该是其中之一，但如果没有，就跳过它 */
		if (!IsA(fc_selem, StatsElem))
			continue;

		fc_name = fc_selem->name;

		if (fc_buflen > 0)
			fc_buf[fc_buflen++] = '_';	/* 在名称之间插入 _ */

		/*
		 * 我们对表达式使用固定的 'expr'，其列名称为空。
		 * 对于索引，这在 ChooseIndexColumnNames 中处理，
		 * 但我们对统计没有这样的函数，似乎没有添加的必要。
		 * 如果需要更好的名称，用户可以明确指定。
		 */
		if (!fc_name)
			fc_name = "expr";

		/*
		 * 此时我们有 buflen <= NAMEDATALEN。name 应该已经小于
		 * NAMEDATALEN，但为了安全使用 strlcpy。
		 */
		strlcpy(fc_buf + fc_buflen, fc_name, NAMEDATALEN);
		fc_buflen += strlen(fc_buf + fc_buflen);
		if (fc_buflen >= NAMEDATALEN)
			break;
	}
	return pstrdup(fc_buf);
}

/*
 * StatisticsGetRelation：给定统计对象的 OID，获取它所定义的关系的 OID。
 * 使用系统缓存。
 */
Oid StatisticsGetRelation(Oid fc_statId, bool fc_missing_ok)
{
	HeapTuple	fc_tuple;
	Form_pg_statistic_ext fc_stx;
	Oid			fc_result;

	fc_tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_statId));
	if (!HeapTupleIsValid(fc_tuple))
	{
		if (fc_missing_ok)
			return InvalidOid;
		elog(ERROR, "cache lookup failed for statistics object %u", fc_statId);
	}
	fc_stx = (Form_pg_statistic_ext) GETSTRUCT(fc_tuple);
	Assert(fc_stx->oid == fc_statId);

	fc_result = fc_stx->stxrelid;
	ReleaseSysCache(fc_tuple);
	return fc_result;
}
